package com.gjxx.common.utils;

import com.alibaba.fastjson2.JSON;
import com.gjxx.common.enums.ErrorCodeType;
import com.gjxx.common.exception.MsgNotifyException;
import com.gjxx.common.utils.old.ObjectUtil;
import com.sun.org.apache.xerces.internal.impl.dv.util.HexBin;
import lombok.extern.slf4j.Slf4j;

import javax.crypto.Cipher;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.security.*;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;

/**
 * 暂时不使用时间戳
 *
 * @author
 */
@Slf4j
public class SignVerify {


    private static String SHA_TYPE = "SHA-1";

    public static byte[] toBE(long data) {
        byte[] buffer = new byte[8];
        buffer[0] = (byte) (data >>> 56);
        buffer[1] = (byte) (data >>> 48);
        buffer[2] = (byte) (data >>> 40);
        buffer[3] = (byte) (data >>> 32);
        buffer[4] = (byte) (data >>> 24);
        buffer[5] = (byte) (data >>> 16);
        buffer[6] = (byte) (data >>> 8);
        buffer[7] = (byte) (data >>> 0);
        return buffer;
    }

    public static String toHexString(String s) {
        String str = "";
        for (int i = 0; i < s.length(); i++) {
            int ch = s.charAt(i);
            String s4 = Integer.toHexString(ch);
            str = str + s4;
        }
        return str;
    }

    /**
     * 签名
     *
     * @param data      待签名明文
     * @param timestamp 时间戳
     * @param key       签名私钥
     * @param SHAType   签名算法
     * @return 签名后16进制字符
     * @throws Exception
     */
    public static String sign(String data, long timestamp, PrivateKey key, String SHAType) throws Exception {
        MessageDigest md = MessageDigest.getInstance(SHAType);
        md.update(data.getBytes("utf-8"));
//        md.update(toBE(timestamp));
        byte[] hash = md.digest();
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.ENCRYPT_MODE, key);
        byte[] encrypted = cipher.doFinal(hash);
        return HexBin.encode(encrypted);
    }

    /**
     * 签名
     *
     * @param data      待签名明文
     * @param timestamp 时间戳
     * @param key       签名私钥
     * @param SHAType   签名算法
     * @return 签名后16进制字符
     * @throws Exception
     */
    public static String signTime(byte[] data, long timestamp, PrivateKey key, String SHAType) throws Exception {
        MessageDigest md = MessageDigest.getInstance(SHAType);
        md.update(data);
        md.update(toBE(timestamp));
        byte[] hash = md.digest();
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.ENCRYPT_MODE, key);
        byte[] encrypted = cipher.doFinal(hash);
        return HexBin.encode(encrypted);
    }

    /**
     * 签名
     *
     * @param data      待签名明文
     * @param timestamp 时间戳
     * @param key       签名私钥
     * @param SHAType   签名算法
     * @return 签名后16进制字符
     * @throws Exception
     */
    public static String signTime(String data, long timestamp, PrivateKey key, String SHAType) throws Exception {
        MessageDigest md = MessageDigest.getInstance(SHAType);
        md.update(data.getBytes("utf-8"));
        md.update(toBE(timestamp));
        byte[] hash = md.digest();
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.ENCRYPT_MODE, key);
        byte[] encrypted = cipher.doFinal(hash);
        return HexBin.encode(encrypted);
    }

    /**
     * 签名
     *
     * @param data 待签名明文
     * @param key  签名私钥
     * @return 签名后16进制字符
     * @throws Exception
     */
    public static String sign(String data, PrivateKey key) {
        try {
            return sign(data, System.currentTimeMillis(), key, SHA_TYPE);
        } catch (Exception e) {
            throw new MsgNotifyException("签名失败");
        }
    }

    /**
     * 获取私钥（便于测试）
     *
     * @param pfxPath
     * @param password
     * @return
     */
    public static PrivateKey getPrivateKey(String pfxPath, String password) {
        try {

            InputStream pfx = SignVerify.class.getClassLoader().getResourceAsStream(pfxPath);

            KeyStore keyStore = KeyStore.getInstance("PKCS12");
            keyStore.load(pfx, password.toCharArray());
            Enumeration<String> aliases = keyStore.aliases();
            if (!aliases.hasMoreElements()) {
                throw new MsgNotifyException("no alias found");
            }
            String alias = aliases.nextElement();

            //获取签名私钥
            return (PrivateKey) keyStore.getKey(alias, password.toCharArray());

        } catch (Exception e) {
            throw new MsgNotifyException("私钥获取失败");
        }
    }



    /**
     * 获取cert
     *
     * @param pfxPath
     * @param password
     * @return
     */
    public static X509Certificate getCert(String pfxPath, String password) {
        try {
            InputStream pfx = SignVerify.class.getClassLoader().getResourceAsStream(pfxPath);
            KeyStore keyStore = KeyStore.getInstance("PKCS12");
            keyStore.load(pfx, password.toCharArray());
            Enumeration<String> aliases = keyStore.aliases();
            if (!aliases.hasMoreElements()) {
                throw new MsgNotifyException("no alias found");
            }
            String alias = aliases.nextElement();
            //获取cert
            return (X509Certificate) keyStore.getCertificate(alias);
        } catch (Exception e) {
            throw new MsgNotifyException("cert获取失败");
        }
    }

    /**
     * 获取明文摘要
     *
     * @param data 明文
     * @return
     */
    public static byte[] getDigest(String data) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA1");
            md.update(data.getBytes("utf-8"));
//            md.update(SignVerify.toBE(System.currentTimeMillis()));
            //明文数据做摘要
            return md.digest();
        } catch (Exception e) {
            throw new MsgNotifyException("明文摘要获取失败");
        }
    }

    /**
     * 获取明文摘要
     *
     * @param data 明文
     * @return
     */
    public static byte[] getDigest(String data,Long ts,String shaType) {
        try {
            MessageDigest md = MessageDigest.getInstance(shaType);
            md.update(data.getBytes("utf-8"));
            md.update(SignVerify.toBE(ts));
            //明文数据做摘要
            return md.digest();
        } catch (Exception e) {
            throw new MsgNotifyException("明文摘要获取失败");
        }
    }


    /**
     * 获取明文摘要
     *
     * @param data 明文
     * @return
     */
    public static byte[] getDigest(byte[] data,Long ts,String shaType) {
        try {
            MessageDigest md = MessageDigest.getInstance(shaType);
            md.update(data);
            md.update(SignVerify.toBE(ts));
            //明文数据做摘要
            return md.digest();
        } catch (Exception e) {
            throw new MsgNotifyException("明文摘要获取失败");
        }
    }

    /**
     * 对验签串进行解密
     *
     * @param sign    验签sign
     * @param cerPath 公钥证书路径
     * @return
     */
    public static byte[] getCipher(String sign, String cerPath) {
        try {

            InputStream cer = SignVerify.class.getClassLoader().getResourceAsStream(cerPath);

            CertificateFactory factory = CertificateFactory.getInstance("X.509");
            X509Certificate cert = (X509Certificate) factory.generateCertificate(cer);


            byte[] encryptedStr = HexBin.decode(sign);
            Cipher cipher = Cipher.getInstance("RSA");
            cipher.init(Cipher.DECRYPT_MODE, cert);
            //对验签串进行解密
            return cipher.doFinal(encryptedStr);
        } catch (Exception e) {
            e.printStackTrace();
            throw new MsgNotifyException(ErrorCodeType.P_PARTNER_CERTIFICATE_FAILURE);
        }
    }

    /**
     * 签名sign校验
     *
     * @param sign    签名字符串
     * @param data    验签明文
     * @param cerPath 公钥证书路径
     * @return
     */
    public static boolean checkSign(String sign, String data, String cerPath) {
        System.out.println("sign=="+sign);
        System.out.println("data=="+data);
        System.out.println("cerPath=="+cerPath);
        byte[] cipher = getCipher(sign, cerPath);
        byte[] digest = getDigest(data);
        return Arrays.equals(cipher, digest);
    }

    /**
     * 签名sign校验
     *
     * @param sign    签名字符串
     * @param data    验签明文
     * @param cerPath 公钥证书路径
     * @return
     */
    public static boolean checkSign(String sign, String data,Long ts,String shaType, String cerPath) {
        byte[] cipher = getCipher(sign, cerPath);
        byte[] digest = getDigest(data,ts,shaType);
        return Arrays.equals(cipher, digest);
    }

    /**
     * 签名sign校验
     *
     * @param sign    签名字符串
     * @param data    验签明文
     * @param cerPath 公钥证书路径
     * @return
     */
    public static boolean checkSign(String sign, byte[] data,Long ts,String shaType, String cerPath) {
        byte[] cipher = getCipher(sign, cerPath);
        byte[] digest = getDigest(data,ts,shaType);
        return Arrays.equals(cipher, digest);
    }


    /**
     * 根据对象获取加密明文
     * 规则： 按对象字段顺序获取值拼接
     * 空值丢弃
     * 字符型数字值直接拼接
     * 数字类型转字符串拼接
     * 结构体转json拼接
     *
     * @param obj
     * @return
     */
    public static String getSignData(Object obj) {

        StringBuffer valueStr = new StringBuffer();


        try {

            List<Class> classes = ObjectUtil.getClassList(obj);

            for (int i = classes.size(); i > 0; i--) {
                Field[] fields = classes.get(i - 1).getDeclaredFields();
                String[] beanName = new String[fields.length];
                Object[] beanValue = new Object[fields.length];
                Object[] beanSourceType = new Object[fields.length];

                Field.setAccessible(fields, true);
                for (int j = 0; j < beanName.length; j++) {
                    beanName[j] = fields[j].getName();
                    beanValue[j] = fields[j].get(obj);
                    beanSourceType[j] = fields[j].getType().getName();

                    if ("serialVersionUID".equals(beanName[j]) || "sign".equals(beanName[j]) || beanValue[j] == null) {
                        continue;
                    } else if ("java.lang.Integer".equals(beanSourceType[j]) ||
                            "java.lang.Long".equals(beanSourceType[j]) ||
                            "java.lang.BigInteger".equals(beanSourceType[j]) ||
                            "java.lang.Float".equals(beanSourceType[j]) ||
                            "java.lang.Double".equals(beanSourceType[j]) ||
                            "java.lang.BigDecimal".equals(beanSourceType[j]) ||
                            "java.lang.Boolean".equals(beanSourceType[j])) {
                        valueStr.append(beanValue[j]);
                    } else if ("java.lang.String".equals(beanSourceType[j])) {
                        valueStr.append(convertEmpty((String) beanValue[j]));
                    } else {
                        valueStr.append(JSON.toJSONString(beanValue[j]));
                    }
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return valueStr.toString();
    }

    /**
     * 获取验签明文
     *
     * @param param
     * @return
     */
    public static String getSignDataMap(Map<String, Object> param) {

        StringBuffer valueStr = new StringBuffer();

        try {

            for (String key : param.keySet()) {
                if ("sign".equals(key)) {
                    continue;
                } else {
                    Object value = param.get(key);
                    if (value == null) {
                        continue;
                    } else if (String.class.isAssignableFrom(value.getClass())) {
                        valueStr.append(value);
                    } else {
                        valueStr.append(JSON.toJSONString(value));
                    }
                }
            }


        } catch (Exception e) {
            e.printStackTrace();
        }
        return valueStr.toString();

    }


    /**
     * 如果字符串是null,则返回""字符串
     *
     * @param str
     * @return
     */
    private static String convertEmpty(String str) {
        return (str == null || "null".equals(str)) ? "" : str;
    }


}
